package com.moli.iov.gateway.server.handle.impl;

import static com.moli.iov.gateway.util.ResponseBytesUtils.joinRespBytes;

import com.alibaba.fastjson.JSONObject;
import com.moli.iov.base.model.dto.req.VehicleUnauthReqDto;
import com.moli.iov.base.model.dto.res.LoginGateResDto;
import com.moli.iov.base.model.dto.res.TboxBindingDto;
import com.moli.iov.base.remote.IVehicleBaseAuthRemoteService;
import com.moli.iov.base.remote.IVehicleUnauthRemoteService;
import com.moli.iov.gateway.server.NettyChannelManager;
import com.moli.iov.gateway.server.VinThreadPoolExecutor;
import com.moli.iov.gateway.server.handle.Command;
import com.moli.iov.gateway.server.model.KafkaSendData;
import com.moli.iov.gateway.server.model.UpStreamData;
import com.moli.iov.gateway.server.task.DownStreamDataTask;
import com.moli.iov.gateway.server.task.KafkaSendTask;
import com.moli.iov.gateway.util.GatewayUtil;
import com.moli.iov.gateway.util.VinRouter;
import com.moli.iov.protocol.codec.DownStreamData;
import com.moli.iov.protocol.codec.ReqStreamData;
import com.moli.iov.protocol.enums.CommandTag;
import com.moli.iov.protocol.enums.TopicEnums;
import com.moli.iov.rest.RestResponse;
import com.moli.iov.util.SpringContextUtils;
import com.moli.iov.util.StringUtil;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * 登入命令业务
 * </p>
 *
 * @author zql
 * @since 2020/2/21 10:24
 */
public class LoginCommand implements Command {

  private static final Logger logger = LoggerFactory.getLogger(LoginCommand.class);

  private IVehicleUnauthRemoteService vehiceUnauthRemoteService;
  private IVehicleBaseAuthRemoteService vehicleBaseAuthRemoteService;

  private VinRouter vinRouter;
  private VinThreadPoolExecutor downThreadPool;
  private VinThreadPoolExecutor kafkaSendThreadPool;

  public LoginCommand() {
    downThreadPool = SpringContextUtils.getBean("downThreadPool");
    vehiceUnauthRemoteService = SpringContextUtils.getBean("vehicleUnauthRemoteService");
    vehicleBaseAuthRemoteService = SpringContextUtils.getBean("vehicleBaseAuthRemoteService");
    kafkaSendThreadPool = SpringContextUtils.getBean("kafkaSendThreadPool");
    vinRouter = SpringContextUtils.getBean("vinRouter");
  }

  @Override
  public void execute(UpStreamData usd) {
    String vin = null;
    try {
      Channel channel = usd.channel;
      String channelId = channel.id().asLongText().trim();
      byte[] bytes = usd.dataBytes;
      vin = GatewayUtil.getVin(bytes);

      //登入校验
      if (!loginValid(vin, bytes)) {
        channel.close();
        return;
      }

      //查询此channel上是否有vin
      if (isExistVin(vin, channelId)) {
        return;
      }

      //关闭上一次登录的channel
      closeBeforeChannel(vin, channel);
      NettyChannelManager.getInstance().bindVinAndChannel(vin, channel);

      //登录应答 处理登陆,先响应tbox发送数据，在校验登陆
      loginResponse(vin, channel, bytes);

      //设置路由表
      vinRouter.setRoutingTable(vin);

      //发送登入消息
      this.sendMsg(vin, new ReqStreamData(usd.reqDateTime, usd.dataBytes));

      //移除注册通道
      removeRegisterChannel(vin, channel);
    } catch (Exception e) {
      logger.error("[上行报文:车辆登陆][Vin=" + vin + "][登陆失败]", e);
    }
  }

  /**
   * 登入校验 1、iccid是否相同 2、判断 车辆为已授权（调用基础服务） 3、判断 T-BOX为管理内状态 （调用基础服务） 4、判断 vin与tbox的绑定关系，如果
   * 未绑定（VIN+T-BoxID在安装授权中不存在），则拒绝登录，同时生成登入异常记录。 5、判断 车辆是否有登入授权
   */
  private boolean loginValid(String vin, byte[] bytes) {

    try {

      String iccid = GatewayUtil.getICCID(bytes);

      logger.info("登入校验 校验入参 vin: {} iccid:{}", vin, iccid);
      //1、车辆是否有登入授权
      RestResponse<LoginGateResDto> response = vehicleBaseAuthRemoteService
          .getVehicleBaseAuthByVin(vin, iccid);
      logger.info("登入校验  校验返参：{}", JSONObject.toJSON(response));
      if (response == null || !response.flag || response.getData() == null) {
        logger.error("登入校验  车辆是否有登入授权 校验失败 base服务null或false 返参：{}", response);
        return false;
      }

      LoginGateResDto loginGateResDto = response.getData();
      if (!loginGateResDto.getVehicleBaseLoginAuth()) {
        logger.info("登入校验 车辆无 登入授权 vin:{}  loginGateResDto:{}", vin, loginGateResDto);
        return false;
      }

      //2、vin与tbox的绑定关系，如果已绑定，则登入成功
      TboxBindingDto bindingDto = loginGateResDto.getBindingDto();
      if (bindingDto != null && bindingDto.getIccid().equals(iccid)) {
        logger.info("vin与tbox 已绑定 登入校验成功");
        return true;
      }

      //3、vin与tbox未绑定，则登入失败。同时判断T-BOX是否授权、车辆是否授权，两者同时已授权 则生成登入异常记录
      if(loginGateResDto.getVehicleBaseAuth() && loginGateResDto.getVehicleTboxAuth()){
        VehicleUnauthReqDto vehicleUnauthReqDto = new VehicleUnauthReqDto();
        vehicleUnauthReqDto.setVin(vin);
        vehicleUnauthReqDto.setIccid(iccid);
        vehicleUnauthReqDto.setDeviceId(loginGateResDto.getDeviceId());
        RestResponse restResponse = vehiceUnauthRemoteService
            .saveVehicleUnauth(vehicleUnauthReqDto);
        logger.info("生成登入异常记录 入参：{} 出参：{}",
            JSONObject.toJSON(vehicleUnauthReqDto), JSONObject.toJSON(restResponse));
      }
    } catch (Exception e) {
      logger.error("登入校验 异常 不做登入校验，直接登入成功 ", e);
      return true;
    }
    logger.info("登入校验失败 vin:{}", vin);
    return false;
  }

  /**
   * 查询此channel上是否有vin
   */
  private boolean isExistVin(String vin, String channelId) {

    String vin1 = NettyChannelManager.getInstance().getLonginVinByChannel(channelId);
    if (StringUtil.isNotBlank(vin1) && !vin.equals(vin1)) {
      logger.error("[上行报文][该通道已有车辆登录上报数据，不准许其他VIN再次登录！][Vin=" + vin + "]");
      return true;
    }

    return false;
  }


  /**
   * 关闭上一次登录的channel, 同一时刻只允许最新有channel有效 同一个VIN不同的channel登陆，后者挤掉前者
   */
  private void closeBeforeChannel(String vin, Channel channel) {
    Channel vinChannel = NettyChannelManager.getInstance().getLoginChannelByVin(vin);
    if (vinChannel != null && !vinChannel.id().asLongText().equals(channel.id().asLongText())) {
      logger.error("[上行报文][该车辆已经登陆，再次登陆以前连接将失效][Vin=" + vin + "]"
          + "老通道id:" + vinChannel.id().asShortText() + "  新通道id:" + channel.id().asShortText());
      vinChannel.close();
    }
  }

  /**
   * 登录应答 处理登陆,先发送数据，在校验登陆
   */
  private void loginResponse(String vin, Channel channel, byte[] bytes) {
    DownStreamData downStreamData = new DownStreamData(vin, joinRespBytes(bytes, true));
    downThreadPool.execute(vin, new DownStreamDataTask(downStreamData));
  }

  /**
   * 移除注册通道--存在注册完成紧接着发登陆报文的情况，需要移除注册使用的channel
   */
  private void removeRegisterChannel(String vin, Channel channel) {
    Channel registerChannel = NettyChannelManager.getInstance().getRegisterChannelByVin(vin);
    if (registerChannel != null && registerChannel.id().asLongText()
        .equals(channel.id().asLongText())) {
      NettyChannelManager.getInstance().removeRegisterChannel(registerChannel);
    }
  }

  /**
   * 发送消息
   */
  private void sendMsg(String vin, Object object) {
    try {
      String topic = TopicEnums.getTopic(CommandTag.VEHICLE_LOGIN.getV());
      KafkaSendData kafkaSendData = new KafkaSendData(topic, vin, object);
      kafkaSendThreadPool.execute(vin, new KafkaSendTask(kafkaSendData));
    } catch (Exception e) {
      logger.error("kafka发送异常", e);
    }
  }
}
